#include "lruDirectIO.h"

/**
 * 用于测试直接IO的性能
 * 实现了直接读写与去重读写两种方式, 去重读写的所有元数据(除了哈希表)均放在磁盘
 * 
 * 去重元数据结构: entry = 指纹fp + 物理块号pbn + 引用计数ref, 一个entry对应一个非重复的物理块, 多个entry凑成一个块写在磁盘上
 * 哈希表: fp -> entryid (f2p), lbn -> entryid (l2p)
 * 读流程(读两次):
 *      查l2p哈希表, 得到entryid, 读取对应磁盘块, 获取entry, 从entry中获取pbn, 读取物理块. 共读两次
 * 写流程:
 * 最高两读三写(修改非重复), 偶尔再加一写, 最少一读一写(添加重复)
 *      1. 查f2p哈希表
 *          如果查到(重复), 则从磁盘中读取entry, ++ref, 并写回磁盘(一读一写)
 *          如果没查到(非重复), 则从磁盘中读取curr_blk(还未满的block), 添加entry, 并将数据与元数据写回磁盘(一读两写), add哈希表项.
 *              如果curr_blk已满, 则申请新的block(加一写)
 *      2. 查l2p哈希表
 *          如果查到(修改), 则从磁盘中读取entry, --ref, 并写回磁盘(一读一写), 则replace哈希表项
 *          如果没查到(添加), 则add哈希表项
 * 
 * BUGS: 里面很多地方直接使用BLKSZ代替sizeof(struct metadata_blk)
*/

/**
 * 将entryid对应的block读入tmp_blk, 并返回entryid对应的entry
*/
struct metadata_entry * read_tmp_blk(bn_t entryid) {
    ssize_t rd = pread(meta_fd, tmp_blk, BLKSZ, BLKSZ * BINDEX(entryid));
    if (rd != BLKSZ || tmp_blk->blockid != BINDEX(entryid)) {
        perror("read tmp block");
        printf("read: %ld, blockid: %d, entryid: %d\n", rd, tmp_blk->blockid, entryid);
        exit(1);
    }
    return tmp_blk->entries + BOFFSET(entryid);
}

void write_tmp_blk() {
    ssize_t wr = pwrite(meta_fd, tmp_blk, BLKSZ, BLKSZ * tmp_blk->blockid);
    if (wr != BLKSZ) {
        perror("write tmp block");
        exit(1);
    }
}

bn_t allocate_new_data_block(void) {
    return datacnt++;
}

bn_t allocate_new_meta_block(void) {
    return metacnt++;
}

void new_curr_block(void) {
    allocate_align((void **)&curr_blk, sizeof(struct metadata_blk));
    curr_blk->blockid = allocate_new_meta_block();
    curr_blk->valid_num = 0;
}

/**
 * 将blk放入LRU中, 仅仅放指针, 不会新建blk
*/
void put_blk_in_LRU(struct metadata_blk* blk) {
    struct meta_lru *p;

    // 不需要LRU的情况
    if (!metablk_inMem_max) {
        bWriteMeta(blk->blockid, (char *)blk);
        free(blk);
        return;
    }

    // printf("curr_blk: %lx\n", curr_blk);
    p = Calloc(1, sizeof(struct meta_lru));
    LIST_INSERT(p, &meta_lru_head);
    p->blk = blk;
    ++metablk_inMem;

    if (metablk_inMem > metablk_inMem_max) {
        // 从LRU中删除最后一个
        p = meta_lru_head.prev;
        LIST_REMOVE(p);

        // 刷回磁盘
        bWriteMeta(p->blk->blockid, (char *)p->blk);
        free(p->blk);
        free(p);
        --metablk_inMem;
    }
    // printf("curr_blk: %lx\n", curr_blk);
    return;
}

/**
 * 寻找entryid对应的entry, 并进行LRU
*/
struct metadata_entry* find_entry(bn_t entryid) {
    struct meta_lru *p;
    struct metadata_entry *entry;
    bn_t bindex = BINDEX(entryid), boffset = BOFFSET(entryid);
    
    // 头块
    if (bindex == curr_blk->blockid) {
        // printf("curr found\n");
        return curr_blk->entries + boffset;
    }
    // 遍历meta_lru_head
    for (p = meta_lru_head.next; p != &meta_lru_head; p = p->next) {
        if (p->blk->blockid == bindex) {
            // 移到头部
            LIST_REMOVE(p);
            LIST_INSERT(p, &meta_lru_head);
            // printf("LRU in mem found\n");
            return p->blk->entries + boffset;
        }
    }
    // 从磁盘读取
    // printf("from disk\n");
    entry = read_tmp_blk(entryid);
    // 加入LRU
    put_blk_in_LRU(tmp_blk);
    allocate_align((void **)&tmp_blk, sizeof(struct metadata_blk));
    return entry;
}

int write_dedup(bn_t lbn, const char *content) {
    struct fp2PBN *f2p, *new_f2p;
    struct LBN2PBN *l2p, *new_l2p;
    struct metadata_entry *entry;
    uint32_t valid_num, entryid;
    // printf("==========curr: %lx, lru: %lx %lx\n", curr_blk, meta_lru_head.next->blk, meta_lru_head.prev->blk);
    // calc hash
    char *fp = Calloc(1, FPSZ * sizeof(char));
    calcHash(fp, content);

    // 根据fp查hash表
    HASH_FIND(hh, f2p_ht, fp, FPSZ, f2p);
    if (f2p) {
        // add ref
        // printf("add cnt entryid: %d\n", f2p->entryid);
        find_entry(f2p->entryid)->ref++;
        entryid = f2p->entryid;
        dupcnt++;
    } else {
        // printf("add new entry\n");
        // printf("validnum: %d\n", curr_blk->valid_num);
        if (curr_blk->valid_num == ENTRY_PER_BLK) {
            // curr_block 已满, 需要重新申请新的block
            put_blk_in_LRU(curr_blk);
            new_curr_block();
            // printf("%lx, %lx\n", curr_blk, meta_lru_head.next->blk);
        }
        valid_num = curr_blk->valid_num++;

        // 设置新的entry, ref, 并将内容写入磁盘
        entry = curr_blk->entries + valid_num;
        // memcpy(entry->fp, fp, FPSZ); // BUGS: 这个东西在目前的程序中完全没用
        entry->pbn = allocate_new_data_block();
        entry->ref = 1;
        
        bWriteData(entry->pbn, content);

        // add to f2p hashmap
        entryid = GEN_ENTRYID(curr_blk->blockid, valid_num);
        // printf("add entry entryid: %x, %d, %d\n", entryid, tmp_blk->blockid, valid_num);
        // printf("valid_num: %d\n", tmp_blk->valid_num);

        new_f2p = Calloc(1, sizeof(struct fp2PBN));
        new_f2p->entryid = entryid;
        memcpy(new_f2p->fp, fp, FPSZ);
        HASH_ADD(hh, f2p_ht, fp, FPSZ, new_f2p);
    }
    
    // 根据lbn查hash表
    new_l2p = Calloc(1, sizeof(struct LBN2PBN));
    new_l2p->entryid = entryid;
    new_l2p->lbn = lbn;

    HASH_FIND(hh, l2p_ht, &lbn, sizeof(bn_t), l2p);
    if (l2p) {
        HASH_REPLACE(hh, l2p_ht, lbn, sizeof(bn_t), new_l2p, l2p);
        // 如果是修改, 需要减引用
        find_entry(l2p->entryid)->ref--;
        // printf("modify entry entryid: %d, lbn: %d\n", l2p->entryid, lbn);
        editcnt++;
    } else {
        // printf("add l2p entry entryid: %d, lbn: %d\n", new_l2p->entryid, lbn);
        HASH_ADD(hh, l2p_ht, lbn, sizeof(bn_t), new_l2p);
    }

    free(fp);
    return BLKSZ;
}

int read_dedup(bn_t lbn, char *content) {
    struct LBN2PBN *l2p;
    struct metadata_entry *entry;
    int ret;
    // 根据lbn查hash表
    HASH_FIND(hh, l2p_ht, &lbn, sizeof(bn_t), l2p);
    if (!l2p) {
        return 0;
    }
    // printf("l2p->entryid: %d\n", l2p->entryid);

    // 获取entry
    entry = find_entry(l2p->entryid);

    // 搞到entry 读取数据
    if (!entry) {
        printf("metadata entry %d not found entry\n", l2p->entryid);
        return -1;
    }

    // 如果读不出来, 同样会返回0
    // printf("entry->pbn: %d\n", entry->pbn);
    ret = pread(data_fd, content, BLKSZ, BLKSZ * (entry->pbn));
    return ret;
}

void init_dedup() {
    new_curr_block();
    allocate_align((void **)&tmp_blk, sizeof(struct metadata_blk));
}

void exit_dedup() {
    #ifdef DELAY_WRITE
    buffer_queue_t *p = buffer_queue_head, *prev;
    printf("flushing buffer queue\n");
    buffered_write_cnt = 0;

    while (p) {
        buffered_write_cnt++;
        Pwrite(meta_fd_bak, p->buf, p->size, p->offset);
        prev = p;
        p = p->next;
        free(prev->buf);
        free(prev);
        if (buffered_write_cnt % FSYNCSTEP == 0) {
            fsync(meta_fd_bak);
        }
    }
    #endif
}

int main(int argc, char **argv) {
    int r_fd;
    char *buf;//, *buf1, *buf2;
    int lineCnt = 0, restrictLine, opsCnt = 0, i;
    ssize_t rwSZ;
    int rdFail, rCnt, wCnt;

    char *rdBuf, *md5Buf, *pLine;

    // blkparse
    unsigned long ts;
    unsigned long pid;
    char          pname[128];
    unsigned long lba;
    unsigned long blks;
    char          rw;
    int           major, minor;
    char          md5[MD5_READ_SZ + 1];

    // init
    if (posix_memalign((void **)&buf, BLKSZ, BLKSZ * 2)) {
        perror("posix_memalign failed");
        exit(1);
    }

    const int rdBufSize = 1024 * 33;
    rdBuf = malloc(rdBufSize);
    md5Buf = malloc(rdBufSize);

    rdFail = rCnt = wCnt = 0;

    if (argc != 6) {
        perror("usage: ./a.out blkparse_file data_file meta_file buffered_meta_file lineCnt\n");
        exit(1);
    }
    
    r_fd = Open(argv[1], O_RDONLY);
    data_fd = OpenMode(argv[2], O_RDWR | O_CREAT | O_TRUNC | O_DIRECT, 0666);
    meta_fd = OpenMode(argv[3], O_RDWR | O_CREAT | O_TRUNC | O_DIRECT, 0666);

    /**
     * 正常的活动需要读写元数据文件, 所以如果想要延迟元数据写
     * 就需要hook掉meta_fd, 将其放入普通的文件(非目标文件系统)进行读写
    */
    #ifdef DELAY_WRITE
    meta_fd_bak = meta_fd;
    meta_fd = OpenMode(argv[4], O_RDWR | O_CREAT | O_TRUNC, 0666);
    #endif

    restrictLine = atoi(argv[5]); // 如果不限制则返回值为0

    init_dedup();
    // readlines
    fill_buffer(rdBuf, rdBufSize, r_fd);
    while ((pLine = readline_buffered(rdBuf, rdBufSize, r_fd)) != NULL) {
        lineCnt++;
        memset(pname, 0, 128);
        memset(md5, 0, MD5_READ_SZ + 1);

        sscanf(pLine, "%lu %lu %s %lu %lu %c %d %d %s", &ts, &pid, pname, &lba, &blks, &rw, &major, &minor, md5Buf);
        lba >>= 3;
        for (i = 0; i < blks / 8; i++) {
            opsCnt++;
            if (rw == 'W') {
                wCnt++;
                memcpy(md5, md5Buf + MD5_STEP * i, MD5_READ_SZ);

                memset(buf, 0, BLKSZ);
                strncpy(buf, md5, BLKSZ);
                #ifdef DEDUP
                rwSZ = write_dedup(lba + i, buf);
                #else
                rwSZ = pwrite(data_fd, buf, BLKSZ, (lba + i) * BLKSZ);
                #endif
                if (rwSZ != BLKSZ) {
                    perror("write");
                    exit(1);
                }

                if (wCnt % FSYNCSTEP == 0) {
                    fsync(meta_fd);
                    fsync(data_fd);
                }
            } else if (rw == 'R') {
                rCnt++;
                #ifdef DEDUP
                rwSZ = read_dedup(lba + i, buf);
                #else
                rwSZ = pread(data_fd, buf, BLKSZ, (lba + i) * BLKSZ);
                #endif
                if (rwSZ != BLKSZ) {
                    rdFail++;
                }
            } else {
                printf("not RW operation\n");
            }
        }
        if (lineCnt % 10000 == 0) printf("lineCnt: %d, opsCnt: %d\n", lineCnt, opsCnt);
        // 如果不限制则restrictLine atoi为0
        if (restrictLine && lineCnt >= restrictLine) break;
    }

    exit_dedup();
    printf("all %d lines %d ops test passed\n", lineCnt, opsCnt);
    printf("rCnt: %d, wCnt: %d, rdFail: %d\n", rCnt, wCnt, rdFail);
    printf("datacnt: %d, metacnt: %d\n", datacnt, metacnt);
    printf("dupcnt: %d, editcnt: %d\n", dupcnt, editcnt);
    #ifdef DELAY_WRITE
    printf("buffered_write_cnt: %d\n", buffered_write_cnt);
    #endif
    if (wCnt != 0) printf("dupRate: %.1f%%, editRate: %.1f%%\n", (float)dupcnt / (float)wCnt * 100, (float)editcnt / (float)wCnt * 100);

    close(r_fd);
    close(data_fd);
    close(meta_fd);
    #ifdef DELAY_WRITE
    close(meta_fd_bak);
    #endif
    free(buf);
    // free(buf1);
    // free(buf2);
    free(rdBuf);
    free(md5Buf);
    if(tmp_blk) free(tmp_blk);
    return 0;
}
